<?php
/**
 * Created by PhpStorm.
 * User: Drunk
 * Date: 2017-4-23
 * Time: 12:11
 */

namespace drunk\socket;

/**
 * Class server // socket 服务器
 * @package drunk\socket
 */
abstract class server{
    private $config;

    private static $map = [],
        $stopped = 0;

    /**
     * server constructor.
     * @param string $host
     */
    function __construct($host) {
        $this->host = trim($host);
        $this->init();
    }

    /**
     * 析构, 释放资源
     */
    function __destruct(){
        event_free($this->config['event']); // 释放注册事件
        event_base_free($this->config['base']); // 释放事件
        @fclose($this->config['socket']); // 关闭socket
        unset(self::$map[$this->host]); // 从map中移除
    }

    /**
     * 停止socket服务器
     */
    public function stop(){
        $this->__destruct();
        if (empty(self::$map)) self::$stopped = 1; // 若已无监听map, 则退出轮循
    }

    /**
     * 绑定客户端服务器事件
     * @param string $event         事件名
     * @param callback $callback    事件回调
     */
    public function on($event, $callback){
        $method = 'on_' . $event;
        $this->config['callback'][$method] = $callback;
    }

    /**
     * 向客户端连接写数据
     * @param int $connection_id
     * @param string $streams
     * @return bool
     */
    public function write($connection_id, $streams){
        // 如果自定义写入回调返回0, 则不会继续向客户端发送数据
        if (0 === @call_user_func($this->config['callback']['on_write'], $streams, $connection_id, $this)) return false;
        $streams = static::encode($streams);
        return event_buffer_write($this->config['connections'][$connection_id]['buffer'], $streams);
    }

    /**
     * 断开指定id的客户端连接
     * @param int $connection_id
     */
    public function disconnect($connection_id){
        event_buffer_disable($this->config['connections'][$connection_id]['buffer'], EV_READ | EV_WRITE);
        event_buffer_free($this->config['connections'][$connection_id]['buffer']);
        @fclose($this->config['connections'][$connection_id]['connection']);
        unset(self::$map[$this->host]['connections'][$connection_id]); // 从map中移除
    }

    /**
     * 事件轮循监听
     */
    public static function listen(){
//        $timer = time();
        while (1) {
//            $timer_last = time();
//            $is_ping_time = self::$interval_ping && $timer_last - $timer > self::$interval_ping;
//            if ($is_ping_time) $timer = $timer_last; // 重置上次ping时间
            foreach (self::$map as $v) event_base_loop($v['base'], EVLOOP_NONBLOCK); // 不阻塞
            if (self::$stopped) return;
            usleep(1); // 休息下
        }
    }

    /**
     * 初始化
     */
    private function init(){
        if (array_key_exists($this->host, self::$map)) return; // 若该主机已绑定过, 则直接取记录的资源句柄不重复绑定
        $socket = stream_socket_server($this->host);
        stream_set_blocking($socket, 0); // 非阻塞
        $base = event_base_new();
        $event = event_new();
        self::$map[$this->host] = ['handle' => $this, 'callback' => [
            'on_connect' => [&$this, 'on_connect'],
            'on_read' => [&$this, 'on_read'],
            'on_write' => [&$this, 'on_write'],
            'on_error' => [&$this, 'on_error'],
        ], 'host' => $this->host, 'socket' => $socket, 'base' => $base, 'event' => $event, 'connections' => []];
        $this->config = &self::$map[$this->host];
        event_set($event, $socket, EV_READ | EV_PERSIST, ['self', 'accept'], $this->host);
        event_base_set($event, $base);
        event_add($event);
//        event_base_loop($base);
    }

    /**
     * 客户端连接回调与数据处理回调绑定
     * @param resource $socket
     * @param int $flag
     * @param string $host
     */
    protected static function accept($socket, $flag, $host){
        $base = self::$map[$host]['base'];
        $connection = stream_socket_accept($socket);
        $connection_id = (int) $connection;
        stream_set_blocking($connection, 0);
        $buffer = event_buffer_new($connection, ['self', 'ev_read'], null, ['self', 'ev_error'], [$host, $connection_id]);
        self::$map[$host]['connections'][$connection_id] = ['connection_id'=>$connection_id, 'connection' => $connection, 'buffer' => $buffer];
        event_buffer_base_set($buffer, $base);
        event_buffer_watermark_set($buffer, EV_READ, 0, 0xffffff);
//        event_buffer_priority_set($buffer, 10);
        event_buffer_enable($buffer, EV_READ | EV_PERSIST);
    }

    /**
     * 数据读取回调
     * @param resource $buffer
     * @param array $config
     */
    protected static function ev_read($buffer, $config){
        list($host, $connection_id) = $config;
        $streams = '';
        while ($read = event_buffer_read($buffer, 1024)) $streams .= $read;
        if (empty(self::$map[$host]['connections'][$connection_id]['ip'])) { // 未与客户端握手
            $response = static::handshake($buffer, $streams, self::$map[$host]['connections'][$connection_id]);
            self::header_fill(self::$map[$host]['connections'][$connection_id]); // 补全header
            if (false !== $response) @call_user_func(self::$map[$host]['callback']['on_connect'], self::$map[$host]['connections']['header'], $connection_id, self::$map[$host]['handle']);
        } else {
            $streams = static::decode($streams); // 若解析失败或者非有效传递数据, 则不回调
            if ($streams !== false) @call_user_func(self::$map[$host]['callback']['on_read'], $streams, $connection_id, self::$map[$host]['handle']);
        }
    }

    /**
     * 补全header信息
     * @param array $connection_info
     */
    private static function header_fill(&$connection_info){
        $connection_info['header']['remote_host'] = stream_socket_get_name($connection_info['connection'], 1);
        $connection_info['header']['remote_ip'] = preg_replace('/:\d+$/', '', $connection_info['header']['remote_host']);
        $connection_info['header']['connection_id'] = $connection_info['connection_id'];
    }

    /**
     * 连接错误回调
     * @param resource $event
     * @param string $error
     * @param array $config
     */
    protected static function ev_error($event, $error, $config){
        list($host, $connection_id) = $config;
        // 如果自定义错误回调返回0, 则不会继续释放资源
        if (0 === @call_user_func(self::$map[$host]['callback']['on_error'], $error, $connection_id, self::$map[$host]['handle'])) return false;
        return call_user_func([self::$map[$host]['handle'], 'disconnect'], $connection_id);
    }
}